package org.JWCB.friend.service.user;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.JWCB.common.core.constants.CacheConstants;
import org.JWCB.common.core.constants.HttpConstants;
import org.JWCB.common.core.constants.UserConstants;
import org.JWCB.common.core.utils.ThreadLocalUtil;
import org.JWCB.common.file.service.OSSService;
import org.JWCB.friend.domain.user.dto.*;
import org.JWCB.common.core.domain.entity.LoginUser;
import org.JWCB.friend.domain.user.entity.User;
import org.JWCB.common.core.domain.vo.LoginUserVo;
import org.JWCB.common.core.domain.vo.R;
import org.JWCB.common.core.enums.ResultCode;
import org.JWCB.common.core.enums.UserIdentity;
import org.JWCB.common.core.exception.ServiceException;
import org.JWCB.friend.domain.user.vo.UserDetailVo;
import org.JWCB.friend.manager.UserCacheManager;
import org.JWCB.friend.mapper.user.UserMapper;
import org.JWCB.common.message.service.AliSmsService;
import org.JWCB.common.redis.service.RedisService;
import org.JWCB.common.security.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@RefreshScope
@Slf4j
@Service
public class UserService {

    @Autowired
    private RedisService redisService;

    @Autowired
    private AliSmsService aliSmsService;

    @Value("${sms.code-expiration:5}")
    private Long codeExpiration;


    @Value("${file.oss.avatarDir}")
    private String avatarDir;


    @Value("${sms.send-limit:10}")
    private Integer sendLimit;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private TokenService tokenService;

    @Value("${jwt.secret}")
    private String secret;


    @Value("${sms.is-send:false}")
    private boolean isSend;

    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    /**
     * 指定手机号发送验证码
     * @param userSendCodeDto 手机号
     */

    public boolean sendCode(UserSendCodeDto userSendCodeDto) {
        // 判断在一分钟内只能获取一次
        String phoneCodeKey = getPhoneCodeKey(userSendCodeDto.getPhone());
        Long expire = redisService.getExpire(phoneCodeKey, TimeUnit.SECONDS);
        if(expire != null && codeExpiration * 60 -  expire <= 60) {
            throw new ServiceException(ResultCode.FREQUENT_OPERATION);
        }
        // 判断在一天内只能获取指定的限制次数
        String codeTimeKey = getCodeTimeKey(userSendCodeDto.getPhone());
        Long codeTime = redisService.getCacheObject(codeTimeKey,Long.class);
        if(codeTime != null && codeTime >= sendLimit) {
            throw new ServiceException(ResultCode.MAXIMUM_NUM_OF_TIMES);
        }
        String code = isSend ? RandomUtil.randomNumbers(6) : "123456";
        if(isSend) {
            boolean res = aliSmsService.sendMobileCode(userSendCodeDto.getPhone(),code);
            if(!res) {
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }
        redisService.setCacheObject(phoneCodeKey,code,codeExpiration,TimeUnit.MINUTES);
        redisService.increment(codeTimeKey);
        if(codeTime == null) {
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withNano(0));
            redisService.expire(codeTimeKey,seconds,TimeUnit.SECONDS);
        }
        return true;
    }


    /**
     * 获取redis中存储的手机验证码的key
     * @param phone 手机
     * @return key
     */
    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    /**
     * 获取redis中存储的手机号单日的获取验证码次数的key
     * @param phone 手机号
     * @return key
     */
    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }


    /**
     * 用户登录
     * @param userLoginDto 登录数据(手机号+验证码)
     * @return token
     */
    public String codeLogin(UserLoginDto userLoginDto) {
        checkCode(userLoginDto.getCode(), userLoginDto.getPhone());
        // 判断是否是新用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone,userLoginDto.getPhone()));
        if(user == null) {
            // 新用户 -> 注册
            user = new User();
            user.setPhone(userLoginDto.getPhone());
            user.setNickName(getRandomNickName());
            userMapper.insert(user);
        }
        return tokenService.createToken(user.getUserId(),user.getNickName(),secret, UserIdentity.ORDINARY.getValue(),user.getHeadImage());
    }


    /**
     * 校验验证码
     * @param code 验证码
     */
    public boolean checkCode(String code, String phone) {
        String phoneCodeKey = getPhoneCodeKey(phone);
        String oldCode = redisService.getCacheObject(phoneCodeKey,String.class);
        if(StrUtil.isEmpty(oldCode)) {
            throw new ServiceException(ResultCode.INVALID_CODE);
        }
        if(!oldCode.equals(code)) {
            throw new ServiceException(ResultCode.INCORRECT_CODE);
        }
        redisService.deleteObject(phoneCodeKey);
        return true;
    }


    /**
     * 得到默认随机的用户名
     */
    private String getRandomNickName() {
        return UserConstants.DEFAULT_NICKNAME_PREFIX + UUID.randomUUID().toString().replace("-","").substring(0,8);
    }


    /**
     * 退出登录功能
     * @param token 身份认证
     * @return 成功 or
     */
    public Boolean logout(String token) {
        return tokenService.logout(token,secret);
    }

    /**
     *
     * @param token
     * @return
     */
    public R<LoginUserVo> info(String token) {
        LoginUser loginUser = tokenService.getLoginUser(token,secret);
        if(loginUser == null) {
            return R.fail();
        }
        LoginUserVo loginUserVo = new LoginUserVo();
        loginUserVo.setNickName(loginUser.getNickName());
        if(StrUtil.isNotEmpty(loginUser.getHeadImage())) {
            loginUserVo.setHeadImage(downloadUrl + avatarDir + loginUser.getHeadImage());
        }
        return R.ok(loginUserVo);
    }

    /**
     * 获取用户详情
     * @return 用户详情
     */
    public R<UserDetailVo> detail() {
        Long userId = getUserId();
        UserDetailVo userDetailVo = userCacheManager.getUserInfo(userId);
        if(userDetailVo == null) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        if(StrUtil.isNotEmpty(userDetailVo.getHeadImage())) {
            userDetailVo.setHeadImage(downloadUrl + avatarDir +userDetailVo.getHeadImage());
        }
        return R.ok(userDetailVo);
    }

    /**
     * 修改用户信息
     */
    @Transactional
    public int edit(UserEditDto userEditDto) {
        Long userId = getUserId();
        User oldUser = gerUser(userId);
        BeanUtil.copyProperties(userEditDto,oldUser);
        return updateUser(oldUser);
    }


    /**
     * 通过userId获取user
     */
    private User gerUser(long userId) {
        User user = userMapper.selectById(userId);
        if(user == null) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        return user;
    }


    /**
     * 更新头像
     */

    @Transactional
    public int updateHeadImage(HeadImageUploadDto headImageUploadDto) {
        Long userId = ThreadLocalUtil.get(UserConstants.USER_ID,Long.class);
        User user = gerUser(userId);
        user.setHeadImage(headImageUploadDto.getHeadImage());
        return updateUser(user);
    }


    /**
     * 从ThreadLocal获取userId
     */
    private Long getUserId() {
        Long userId = ThreadLocalUtil.get(UserConstants.USER_ID,Long.class);
        if(userId == null) {
            log.info("获取用户信息时候userId为空");
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        return userId;
    }


    /**
     * 更新用户 -- mysql redis
     */
    private int updateUser(User user) {
        int res = userMapper.updateById(user);
        if(res <= 0) {
            return res;
        }
        // 修改redis缓存信息
        userCacheManager.refreshUserCache(user);
        String userKey = ThreadLocalUtil.get(UserConstants.USER_KEY,String.class);
        tokenService.updateLoginUserInfo(user.getHeadImage(),user.getNickName(),userKey);
        return res;
    }


    /**
     * 用户手机号修改
     * @param userUpdatePhoneDto
     * @return
     */
    public int updateUserPhone(UserUpdatePhoneDto userUpdatePhoneDto) {
        checkCode(userUpdatePhoneDto.getCode(), userUpdatePhoneDto.getPhone());
        Long userId = getUserId();
        User user = userMapper.selectById(userId);
        if(user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User checkUser = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, userUpdatePhoneDto.getPhone()));
        if(checkUser != null) {
            throw  new ServiceException(ResultCode.PHONE_HAS_BEAN_BINDING);
        }
        user.setPhone(userUpdatePhoneDto.getPhone());
        return updateUser(user);
    }
}
